/*
 * SmartCam master processor library
 * Coprocessor message generation.
 */

#include <log.h>
#include <scc.h>
#include "globals.h"
#include "management.h"
#include "comms.h"
#include "dispatch.h"
#include "mapping.h"

void scDisInit(void)
{
  lprintf(STREAM(scl.notice), "Initializing operation dispatch");
}

void scDisOperation(sc_op_t *o)
{
  int id=-1;
  sc_arg_t *a;

  if (o->dispatched) return;

  /* Cannot dispatch if we don't know our processor */
  if (!o->processor) return;

  lprintf(STREAM(scl.info), "Dispatching operation %d", o->id);

  /* Dispatch operation */
  scComRemoteOperationEnqueue(o);
  o->dispatched = 1;

  /* Dispatch arguments,  */
  while (tblgetnextelement(&o->arguments, &id, &a))
  {
    scDisVariable(a->instance->variable);

    /* Iterator is invalidated if this argument was destroyed,
     * so start from the beginning. */
    if (!tblget(&o->arguments, id, &a))
      id = -1;
  }

  lprintf(STREAM(scl.crawl), "Operation %d dispatched", o->id);
}

void scDisVariable(sc_var_t *v)
{
  int id=-1, id2=-1, id3=-1;
  sc_buf_t *b;
  sc_arg_t *a, *a2;
  sc_originref_t *r;
  int *d;
  table_t origins;
  sc_transport_t *t;
  int chunksize=0;
  int incomplete=0, outputHasBeenDispatched=0, frameBufferDispatch=0;

  scs_target_t *st;
  scs_operation_t *so;
  scs_target_operation_t *sto;
  scs_target_operation_argument_t *stoa;

  /* Cannot dispatch if we don't know our value */
  if (!v->valid)
    return;

  if (v->type == SC_VAR_STREAM && v->producer->operation->processor)
  {
    if (tblget(&v->buffers, v->producer->operation->processor->id, NULL))
      outputHasBeenDispatched = 1;
    else if (v->producer->buffer >= SC_FRAME_BUFFER)
      frameBufferDispatch = 1;
  }

  /* Cannot dispatch if any of our instances isn't finalized */
  if (!scManIsVariableFinalized(v))
    incomplete = 1;

  /* Cannot dispatch if this is a stream, and any operation referencing
   * this variable isn't dispatched */
  if (v->type == SC_VAR_STREAM && !incomplete)
  {
    a = v->producer;
    do
    {
      if (!a->operation->dispatched)
	incomplete = 1;
    } while (tblgetnextelement(&v->consumers, &id, &a));
  }

  /* Cannot dispatch if any input of any consumer has a virtual origin */
  if (!incomplete)
    while (tblgetnextelement(&v->consumers, &id, &a))
      while (tblgetnextelement(&a->operation->arguments, &id2, &a2))
	if (a2->intent == SC_INTENT_IN)
	  while (tblgetnextelement(&a2->instance->variable->originrefs, &id3, &r))
	    if (r->origin->isVirtual)
	      incomplete = 1;

  lprintf(STREAM(scl.info), "%sDispatching %s%svariable %d",
	  outputHasBeenDispatched?"Re":"", incomplete?"incomplete ":"",
	  frameBufferDispatch?"frame buffer ":"", v->id);

  /* Apply Pentagram of Protection to avoid destruction */
  v->pentagram = 1;

  if (v->type == SC_VAR_STREAM && !incomplete)
  {
    /* Determine where this variable needs to go, and the buffer sizes for all
     * destinations.
     * *** Note that it is possible to buffer at the output, or at the input. This
     * *** should depend on the memory pressure. We will buffer at input for now.
     */

    /* Get the maximum chunksize of the arguments that reference
     * this variable, and initialize argument buffer sizes. */
    a = v->producer;
    do
    {
      abortOnError(STREAM(scl.err), !a);
      //abortOnError(STREAM(scl.err), a->dispatched);

      if (!a->operation->dispatched)
      {
	abortErrorl((STREAM(scl.err), "Operation %d referencing variable %d is not dispatched!", a->operation->id, v->id));
      }

      /* Get chunksize */
      if (tblget(&scs_targets, a->operation->processor->id, &st))
	if (tblget(&st->operations, a->operation->type, &sto))
	  if (tblget(&sto->arguments, a->id, &stoa))
	  {
	    chunksize = MAX(chunksize, stoa->elementsize * stoa->chunksize);
	    a->buffer = MAX(a->buffer, 
			    stoa->buffer * stoa->elementsize * stoa->chunksize);
	  }
    } while (tblgetnextelement(&v->consumers, &id, &a));

    abortOnNegative(STREAM(scl.err), tblcreate(&origins, sizeof(int)));

    /* Get the maximum delay from each origin referenced by any argument
     * of any operation consuming this stream */
    while (tblgetnextelement(&v->consumers, &id, &a))
    {
      if (!tblget(&scs_operations, a->operation->type, &so)) continue;
      if (!tblget(&so->targets, a->operation->processor->id, &sto)) continue;

      while (tblgetnextelement(&a->operation->arguments, &id2, &a2))
	if (a2->intent == SC_INTENT_IN)
	{
	  if (!tblget(&sto->arguments, a2->id, &stoa)) continue;

	  while (tblgetnextelement(&a2->instance->variable->originrefs, &id3, &r))
	  {
	    if (!tblget(&origins, r->origin->id, &d))
	    {
	      lprintf(STREAM(scl.crawl), "%s.%d referencing origin %d at delay %d",
		      a2->operation->text, a2->id, r->origin->id, r->delay);
	      tbladdnewi(&origins, &d, r->origin->id);
	    }
	    
	    if (r->delay + stoa->state * stoa->elementsize * stoa->chunksize > *d)
	    {
	      *d = r->delay + stoa->state * stoa->elementsize * stoa->chunksize;

	      lprintf(STREAM(scl.crawl), "MAX delay+state from origin %d now %d",
		      r->origin->id, *d);
	    }
	  }
	}
    }

    /* Now, update the argument buffers to negate the delays */
    while (tblgetnextelement(&v->consumers, &id, &a))
    {
      if (!tblget(&scs_operations, a->operation->type, &so)) continue;
      if (!tblget(&so->targets, a->operation->processor->id, &sto)) continue;

      while (tblgetnextelement(&a->operation->arguments, &id2, &a2))
	if (a2->instance->variable->type == SC_VAR_STREAM && a2->intent == SC_INTENT_IN)
	{
	  if (!tblget(&sto->arguments, a2->id, &stoa)) continue;

	  while (tblgetnextelement(&a2->instance->variable->originrefs, &id3, &r))
	  {
	    tblget(&origins, r->origin->id, &d);
	    a2->buffer = MAX(a2->buffer,
			     (*d - (r->delay +
				    stoa->state * stoa->elementsize * stoa->chunksize)));

	    lprintf(STREAM(scl.crawl), "%s.%d buffer %d",
		    a2->operation->text, a2->id, a2->buffer);
	  }
	}
    }
      
    abortOnNegative(STREAM(scl.err), tbldestroy(&origins, 1));

    /* Now implement the buffer sizes */
    a = v->producer;
    do
    {
      if (!tblget(&v->buffers, a->operation->processor->id, &b))
      {
	b = scManAddBuffer(v, a->operation->processor);
	b->size = a->buffer + chunksize;
      }
      else
      {
	b->size = MAX(b->size, a->buffer + chunksize);
      }    
    } while (tblgetnextelement(&v->consumers, &id, &a));

    /* Dispatch transports */

    /* Create intermediate buffers */
    while (tblgetnextelement(&v->transports, &id, &t))
    {
      tblget(&v->buffers, t->source->id, &b);
      if (!b) scManAddBuffer(v, t->source);

      tblget(&v->buffers, t->destination->id, &b);
      if (!b) scManAddBuffer(v, t->destination);
    }

    /* Setup buffers */
    while (tblgetnextelement(&v->buffers, &id, &b))
    {
      if (!outputHasBeenDispatched || id != v->producer->operation->processor->id)
	scComRemoteBufferSetup(b);
    }

    /* Create transports */
    while (tblgetnextelement(&v->transports, &id, &t))
      scComRemoteTransportSetup(tblget(&v->buffers, t->source->id, NULL),
				tblget(&v->buffers, t->destination->id, NULL),
				t->weight);
  }
  else if (v->type == SC_VAR_STREAM && frameBufferDispatch && !outputHasBeenDispatched)
  {
    /* We don't have enough information to do a proper dispatch, but the
     * output buffer is frame sized, so dispatch that one anyway */
    
    /* Create buffer */
    b = scManAddBuffer(v, v->producer->operation->processor);
    b->size = v->producer->buffer;    

    /* Setup buffer */
    scComRemoteBufferSetup(b);
  }

  /* Find operation arguments referencing this variable */
  id = -1;
  a = v->producer;
  do
  {
    if (a)
    {
      if ((a->intent == SC_INTENT_IN || v->type == SC_VAR_STREAM) &&
	  (!incomplete || (frameBufferDispatch && a->intent == SC_INTENT_OUT && v->type == SC_VAR_STREAM)) &&
	  !a->dispatched)
      {
	abortOnError(STREAM(scl.err), !a->operation->dispatched);

	/* Dispatch argument */
	scComRemoteArgumentDistribute(a);
	a->dispatched = 1;
      }
    }
  }
  while (tblgetnextelement(&v->consumers, &id, &a));

  /* Destroy arguments */
  while (tblgetnextelement(&v->consumers, &id, &a))
  {
    if (a->intent == SC_INTENT_IN || v->type == SC_VAR_STREAM)
    {
      scManCheckDestroyArgument(a);

      /* Iterator is invalidated if this argument was destroyed,
       * so start from the beginning. */
      if (!tblget(&v->consumers, id, &a))
	id = -1;
    }
  }

  scDisVariableFinalizations(v);

  v->pentagram = 0;
  scManCheckDestroyVariable(v);
}

void scDisVariableFinalizations(sc_var_t *v)
{
  int id;
  sc_buf_t *b;

  /* Variable must be dispatched */
  if (!scManIsVariableDispatched(v))
    return;

  lprintf(STREAM(scl.info), "Unlocking buffers for variable %d", v->id);

  /* Find buffers */
  id = -1;
  while ((id = tblgetnexti(&v->buffers, id)) >= 0)
  {
    abortOnNULL(STREAM(scl.err), tblget(&v->buffers, id, &b));
    
    /* And unlock */
    if (!b->unlocked)
    {
      scComRemoteBufferFinalize(b);
      b->unlocked = 1;
    }
  }  
}
